{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Chapter 4: Transforming vectors and graphics"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**NOTE** For most of the examples in this chapter, the code is in other .py files in this directory, rather than directly in this Notebook.  You can run the other files in the notebook below, and a PyGame window will open up.  For instance `!python script.py` runs `script.py` as if you're running it from the command line. When you're done admiring the graphics the scripts create, you can close the window and the Jupyter cell will finish computing.\n",
    "\n",
    "Make sure to follow the instructions from Appendix C before beginning this chapter, including\n",
    "\n",
    "`pip install PyGame`\n",
    "\n",
    "`pip install PyOpenGL`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 4.1 Transforming 3D objects"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4.1.1 Drawing a transformed object"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Traceback (most recent call last):\n",
      "  File \"draw_teapot.py\", line 2, in <module>\n",
      "    from draw_model import draw_model\n",
      "  File \"/Users/paul/Dev/math-for-programmers/Chapter 04/draw_model.py\", line 1, in <module>\n",
      "    import pygame\n",
      "ImportError: No module named pygame\n"
     ]
    }
   ],
   "source": [
    "!python draw_teapot.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "pygame 1.9.4\n",
      "Hello from the pygame community. https://www.pygame.org/contribute.html\n"
     ]
    }
   ],
   "source": [
    "!python scale_teapot.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "pygame 1.9.4\n",
      "Hello from the pygame community. https://www.pygame.org/contribute.html\n"
     ]
    }
   ],
   "source": [
    "!python scale_translate_teapot.py"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4.1.2 Composing vector transformations"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "def compose(f1,f2):\n",
    "    def new_function(input):\n",
    "        return f1(f2(input))\n",
    "    return new_function"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "def polygon_map(transformation, polygons):\n",
    "    return [\n",
    "        [transformation(vertex) for vertex in triangle]\n",
    "        for triangle in polygons\n",
    "    ]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "def scale_by(scalar):\n",
    "    def new_function(v):\n",
    "        return scale(scalar, v)\n",
    "    return new_function"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4.1.3 Rotating an object about an axis"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "pygame 1.9.4\n",
      "Hello from the pygame community. https://www.pygame.org/contribute.html\n"
     ]
    }
   ],
   "source": [
    "!python rotate_teapot.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "def rotate_x(angle, vector):\n",
    "    x,y,z = vector\n",
    "    new_y, new_z = rotate2d(angle, (y,z))\n",
    "    return x, new_y, new_z\n",
    "\n",
    "def rotate_x_by(angle):\n",
    "    def new_function(v):\n",
    "        return rotate_x(angle,v)\n",
    "    return new_function"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "pygame 1.9.4\n",
      "Hello from the pygame community. https://www.pygame.org/contribute.html\n"
     ]
    }
   ],
   "source": [
    "!python rotate_teapot_x.py"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4.1.4 Inventing your own geometric transformations"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "def stretch_x(vector):\n",
    "    x,y,z = vector\n",
    "    return (4.*x, y, z)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "pygame 1.9.4\n",
      "Hello from the pygame community. https://www.pygame.org/contribute.html\n"
     ]
    }
   ],
   "source": [
    "!python stretch_teapot.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "pygame 1.9.4\n",
      "Hello from the pygame community. https://www.pygame.org/contribute.html\n"
     ]
    }
   ],
   "source": [
    "!python stretch_teapot_y.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "pygame 1.9.4\n",
      "Hello from the pygame community. https://www.pygame.org/contribute.html\n"
     ]
    }
   ],
   "source": [
    "!python cube_teapot.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "def slant_xy(vector):\n",
    "    x,y,z = vector\n",
    "    return (x+y, y, z)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "pygame 1.9.4\n",
      "Hello from the pygame community. https://www.pygame.org/contribute.html\n"
     ]
    }
   ],
   "source": [
    "!python slant_teapot.py"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4.1.5 Exercises"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Exercise:** Implement a `translate_by` function referred to in section 4.1.2, taking a translation vector as an input and returning a translation function as an output."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "def translate_by(translation):\n",
    "    def new_function(v):\n",
    "        return add(translation,v)\n",
    "    return new_function"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Exercise:** Modify the `compose(f,g)` function to `compose(*args)`, which takes several functions as arguments and returns a new function that is their composition."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "def compose(*args):\n",
    "    def new_function(input): #1\n",
    "        state = input #2\n",
    "        for f in reversed(args): #3\n",
    "            state = f(state) #4\n",
    "        return state\n",
    "    return new_function"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "def prepend(string):\n",
    "    def new_function(input):\n",
    "        return string + input\n",
    "    return new_function\n",
    "\n",
    "f = compose(prepend(\"P\"), prepend(\"y\"), prepend(\"t\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Python'"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "f(\"hon\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Exercise:** Write a `curry2(f)` function that takes in a Python function `f(x,y)` with two arguments and returns a new function that is curried. For instance, once you write `g = curry2(f)`, the two expressions `f(x,y)` and `g(x)(y)` should return the same result."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "def curry2(f):\n",
    "    def g(x):\n",
    "        def new_function(y):\n",
    "            return f(x,y)\n",
    "        return new_function\n",
    "    return g"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [],
   "source": [
    "from vectors import scale"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(2, 4, 6)"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "scale(2,(1,2,3))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [],
   "source": [
    "scale_by = curry2(scale)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(2, 4, 6)"
      ]
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "scale_by(2)((1,2,3))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Exercise:** Write a function `stretch_x(scalar,vector)` that scales the target vector by the given factor, but only in the $x$ direction. Also write a curried version `stretch_x_by` so that `stretch_x_by(scalar)(vector)` returns the same result. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [],
   "source": [
    "def stretch_x(scalar,vector):\n",
    "    x,y,z = vector\n",
    "    return (scalar*x, y, z)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [],
   "source": [
    "def stretch_x_by(scalar):\n",
    "    def new_function(vector):\n",
    "        return stretch_x(scalar,vector)\n",
    "    return new_function\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(5, 2, 3)"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "stretch_x(5,(1,2,3))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(5, 2, 3)"
      ]
     },
     "execution_count": 28,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "stretch_x_by(5)((1,2,3))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 4.2 Linear transformations"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4.2.1 Preserving vector arithmetic"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4.2.2 Picturing linear transformations"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4.2.3 Why linear transformations?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4.2.4 Computing linear transformations"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [],
   "source": [
    "from vectors import add,scale"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [],
   "source": [
    "Ae1 = (1,1,1) #1\n",
    "Ae2 = (1,0,-1)\n",
    "Ae3 = (0,1,1)\n",
    "\n",
    "def apply_A(v): #2\n",
    "    return add( #3\n",
    "        scale(v[0], Ae1),\n",
    "        scale(v[1], Ae2),\n",
    "        scale(v[2], Ae3)\n",
    "    )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "pygame 1.9.4\n",
      "Hello from the pygame community. https://www.pygame.org/contribute.html\n"
     ]
    }
   ],
   "source": [
    "!python linear_transform_teapot.py"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4.2.5 Exercises"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Exercise:** Write a `linear_combination(scalars, *vectors)` that takes a list of scalars and the same number of vectors and returns a single vector. For example, \n",
    "\n",
    "`linear_combination([1,2,3], (1,0,0), (0,1,0), (0,0,1))` \n",
    "\n",
    "should return $$1 \\cdot (1,0,0) + 2 \\cdot (0,1,0) + 3 \\cdot (0,0,1) = (1,2,3).$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [],
   "source": [
    "from vectors import *\n",
    "def linear_combination(scalars,*vectors):\n",
    "    scaled = [scale(s,v) for s,v in zip(scalars,vectors)]\n",
    "    return add(*scaled)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(1, 2, 3)"
      ]
     },
     "execution_count": 33,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "linear_combination([1,2,3], (1,0,0), (0,1,0), (0,0,1))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Exercise:** Write a function `transform_standard_basis(transform)` that takes a 3D vector transformation as an input and outputs the effect it has on the standard basis: it should output a tuple of 3 vectors that are the results of transform acting on $e_1$, $e_2$, and $e_3$, respectively."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [],
   "source": [
    "def transform_standard_basis(transform):\n",
    "    return transform((1,0,0)), transform((0,1,0)), transform((0,0,1))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [],
   "source": [
    "from math import *"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "((1, 0.0, 0.0),\n",
       " (0, 6.123233995736766e-17, 1.0),\n",
       " (0, -1.0, 1.2246467991473532e-16))"
      ]
     },
     "execution_count": 36,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "transform_standard_basis(rotate_x_by(pi/2))"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
